package com.bumptech.glide.load.resource.gif;

import com.bumptech.glide.Glide;
import com.bumptech.glide.gifdecoder.GifDecoder;
import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.gif.drawableability.Callback;
import com.bumptech.glide.load.resource.gif.drawableability.RootShapeElement;
import com.bumptech.glide.util.LogUtil;
import com.bumptech.glide.util.Preconditions;
import ohos.agp.components.Component;
import ohos.agp.components.Image;
import ohos.agp.render.*;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.media.image.PixelMap;
import ohos.media.image.common.Rect;
import ohos.media.image.common.Size;
import org.jetbrains.annotations.VisibleForTesting;

import java.nio.ByteBuffer;

import static com.bumptech.glide.gifdecoder.GifDecoder.TOTAL_ITERATION_COUNT_FOREVER;


/**
 * GifDrawable Gif 绘制
 */
public class GifDrawable extends RootShapeElement
        implements GifFrameLoader.FrameCallback, Animatable {
    /**
     * A constant indicating that an animated drawable should loop continuously.
     */
    @SuppressWarnings("WeakerAccess")
    public static final int LOOP_FOREVER = -1;
    /**
     * A constant indicating that an animated drawable should loop for its default number of times.
     * For animated GIFs, this constant indicates the GIF should use the netscape loop count if
     * present.
     */
    // Public API.
    @SuppressWarnings("WeakerAccess")
    public static final int LOOP_INTRINSIC = 0;

    private final GifState state;
    /**
     * True if the drawable is currently animating.
     */
    private boolean isRunning;
    /**
     * True if the drawable should animate while visible.
     */
    private boolean isStarted;
    /**
     * True if the drawable's resources have been recycled.
     */
    private boolean isRecycled;

    private boolean isVisible = true;
    /**
     * The number of times we've looped over all the frames in the GIF.
     */
    private int loopCount;
    /**
     * The number of times to loop through the GIF animation.
     */
    private int maxLoopCount = LOOP_FOREVER;

    private boolean applyGravity;
    private Paint paint;
    private Rect destRect;


    @SuppressWarnings("deprecation")
    @Deprecated
    public GifDrawable(
            Context context,
            GifDecoder gifDecoder,
            @SuppressWarnings("unused") BitmapPool bitmapPool,
            Transformation<PixelMap> frameTransformation,
            int targetFrameWidth,
            int targetFrameHeight,
            PixelMap firstFrame) {
        this(context, gifDecoder, frameTransformation, targetFrameWidth, targetFrameHeight, firstFrame);
    }


    public GifDrawable(
            Context context,
            GifDecoder gifDecoder,
            Transformation<PixelMap> frameTransformation,
            int targetFrameWidth,
            int targetFrameHeight,
            PixelMap firstFrame) {
        this(
                new GifState(
                        new GifFrameLoader(
                                Glide.get(context),
                                gifDecoder,
                                targetFrameWidth,
                                targetFrameHeight,
                                frameTransformation,
                                firstFrame)));
    }

    GifDrawable(GifState state) {
        this.state = Preconditions.checkNotNull(state);
    }


    GifDrawable(GifFrameLoader frameLoader, Paint paint) {
        this(new GifState(frameLoader));
        this.paint = paint;
    }

    /**
     * getSize
     *
     * @return int
     */
    public int getSize() {
        return state.frameLoader.getSize();
    }

    /**
     * getFirstFrame
     *
     * @return PixelMap
     */
    public PixelMap getFirstFrame() {
        return state.frameLoader.getFirstFrame();
    }

    /**
     * setFrameTransformation
     *
     * @param frameTransformation Transformation<PixelMap>
     * @param firstFrame          PixelMap
     */
    @SuppressWarnings("WeakerAccess")
    public void setFrameTransformation(
            Transformation<PixelMap> frameTransformation, PixelMap firstFrame) {
        state.frameLoader.setFrameTransformation(frameTransformation, firstFrame);
    }

    /**
     * getFrameTransformation
     *
     * @return Transformation<PixelMap>
     */
    public Transformation<PixelMap> getFrameTransformation() {
        return state.frameLoader.getFrameTransformation();
    }

    /**
     * getBuffer
     *
     * @return ByteBuffer
     */
    public ByteBuffer getBuffer() {
        return state.frameLoader.getBuffer();
    }

    /**
     * getFrameCount
     *
     * @return int
     */
    public int getFrameCount() {
        return state.frameLoader.getFrameCount();
    }

    /**
     * Returns the current frame index in the range 0..{@link #getFrameCount()} - 1, or -1 if no frame
     * is displayed.
     *
     * @return the current frame index
     */
    // Public API.
    @SuppressWarnings("WeakerAccess")
    public int getFrameIndex() {
        return state.frameLoader.getCurrentIndex();
    }

    private void resetLoopCount() {
        loopCount = 0;
    }

    /**
     * Starts the animation from the first frame. Can only be called while animation is not running.
     */
    // Public API.
    @SuppressWarnings("unused")
    public void startFromFirstFrame() {
        Preconditions.checkArgument(!isRunning, "You cannot restart a currently running animation.");
        state.frameLoader.setNextStartFromFirstFrame();
        start();
    }

    @Override
    public void start() {
        realStart();
    }

    private void realStart() {
        isStarted = true;
        resetLoopCount();
        if (isVisible) {
            startRunning();
        }
    }

    Image target = null;

    @Override
    public void registView(Component component) {
        target = (Image) component;
    }

    @Override
    public void stop() {
        realStop();
    }

    private void realStop() {
        isStarted = false;
        stopRunning();
    }

    private void startRunning() {
        Preconditions.checkArgument(
                !isRecycled,
                "You cannot start a recycled Drawable. Ensure that"
                        + "you clear any references to the Drawable when clearing the corresponding request.");
        // If we have only a single frame, we don't want to decode it endlessly.
        if (state.frameLoader.getFrameCount() == 1) {
            invalidateSelf();
        } else if (!isRunning) {
            isRunning = true;
            state.frameLoader.subscribe(this);
            invalidateSelf();
        }
    }

    private void stopRunning() {
        isRunning = false;
        state.frameLoader.unsubscribe(this);
    }

    @Override
    public boolean setVisible(boolean visible, boolean restart) {
        Preconditions.checkArgument(
                !isRecycled,
                "Cannot change the visibility of a recycled resource."
                        + " Ensure that you unset the Drawable from your View before changing the View's"
                        + " visibility.");
        isVisible = visible;
        if (!visible) {
            stopRunning();
        } else if (isStarted) {
            startRunning();
        }
        return super.setVisible(visible, restart);
    }

    @Override
    public int getIntrinsicWidth() {
        return state.frameLoader.getWidth();
    }

    @Override
    public int getIntrinsicHeight() {
        return state.frameLoader.getHeight();
    }


    @Override
    public boolean isRunning() {
        return isRunning;
    }

    // For testing.
    void setIsRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }


    @Override
    public void setAlpha(int i) {
        getPaint().setAlpha(i);
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        getPaint().setColorFilter(colorFilter);
    }

    private Rect getDestRect() {
        if (destRect == null) {
            destRect = new Rect();
        }
        return destRect;
    }

    private Paint getPaint() {
        if (paint == null) {
            paint = new Paint();
            paint.setFilterBitmap(true);
            paint.setAntiAlias(true);
            paint.setDither(true);
        }
        return paint;
    }


    // See #1087.
    private Callback findCallback() {
        Callback callback = getHmCallback();
        while (callback instanceof RootShapeElement) {
            callback = ((RootShapeElement) callback).getHmCallback();
        }
        return callback;
    }

    @Override
    public void onFrameReady() {
        // LogUtil.info("doGif", "onFrameReady current pixelmap is  = " + (state.frameLoader.getCurrentFrame() != null));
        if (findCallback() == null) {
            stop();
            invalidateSelf();
            return;
        }
        invalidateSelf();

        if (getFrameIndex() == getFrameCount() - 1) {
            loopCount++;
        }

        if (maxLoopCount != LOOP_FOREVER && loopCount >= maxLoopCount) {
            stop();
        }
    }


    /**
     * Clears any resources for loading frames that are currently held on to by this object.
     */
    public void recycle() {
        isRecycled = true;
        state.frameLoader.clear();
    }

    // For testing.
    boolean isRecycled() {
        return isRecycled;
    }

    /**
     * setLoopCount
     *
     * @param loopCount loopCount
     */
    @SuppressWarnings("WeakerAccess")
    public void setLoopCount(int loopCount) {
        if (loopCount <= 0 && loopCount != LOOP_FOREVER && loopCount != LOOP_INTRINSIC) {
            throw new IllegalArgumentException(
                    "Loop count must be greater than 0, or equal to "
                            + "GlideDrawable.LOOP_FOREVER, or equal to GlideDrawable.LOOP_INTRINSIC");
        }

        if (loopCount == LOOP_INTRINSIC) {
            int intrinsicCount = state.frameLoader.getLoopCount();
            maxLoopCount =
                    (intrinsicCount == TOTAL_ITERATION_COUNT_FOREVER) ? LOOP_FOREVER : intrinsicCount;
        } else {
            maxLoopCount = loopCount;
        }
    }

    @Override
    public void drawToCanvas(Canvas canvas) {
        if (isRecycled) {
            return;
        }

        // LogUtil.info("doGif","drawToCanvas is go canvas is=" + (canvas != null) +"state.frameLoader.getCurrentFrame()=" + (state.frameLoader.getCurrentFrame() != null));
        PixelMap currentFrame = state.frameLoader.getCurrentFrame();
        PixelMapHolder holder = new PixelMapHolder(currentFrame);
        canvas.drawPixelMapHolder(holder, 0, 0, getPaint());
    }

    @Override
    public void onStartDraweeView() {
        realStart();
    }

    @Override
    public void onStopDraweeView() {
        realStop();
    }

    @Override
    public void onDestroyDraweeView() {

    }


    static final class GifState extends ConstantState {
        @VisibleForTesting
        final GifFrameLoader frameLoader;

        GifState(GifFrameLoader frameLoader) {
            this.frameLoader = frameLoader;
        }

    }
}
